package com.ticket.sass.admin.business.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ticket.sass.admin.business.PriceSchemeBusiness;
import com.ticket.sass.admin.dto.input.WeekTimeInput;
import com.ticket.sass.admin.dto.input.pricescheme.PriceSchemeAddInput;
import com.ticket.sass.admin.dto.input.pricescheme.PriceSchemeInput;
import com.ticket.sass.admin.dto.input.pricescheme.PriceSchemeListInput;
import com.ticket.sass.admin.dto.input.pricescheme.PriceSchemeUpdInput;
import com.ticket.sass.admin.dto.output.WeekTimeOutput;
import com.ticket.sass.admin.dto.output.pricecheme.PriceSchemeListOutput;
import com.ticket.sass.admin.dto.output.pricecheme.PriceSchemeSimpOutput;
import com.ticket.sass.admin.model.PageModel;
import com.ticket.sass.admin.service.impl.*;
import com.ticket.sass.admin.util.CinemaIdUtil;
import com.ticket.sass.admin.util.EscapeUtil;
import com.ticket.sass.admin.util.GsonUtil;
import com.ticket.sass.admin.util.ModelUtil;
import com.ticket.sass.common.entity.tenant.*;
import com.ticket.sass.common.entity.tenant.enums.PriceSchemeActivateType;
import com.ticket.sass.common.entity.tenant.enums.PriceSchemeAvailablePeriod;
import com.ticket.sass.common.entity.tenant.enums.PriceSchemeMemberPriceType;
import com.ticket.sass.common.exception.enums.ErrorCode;
import com.ticket.sass.common.exception.exception.BadRequestException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author ywb
 * @date 2024-03-12 17:31
 */
@Slf4j
@Service
@AllArgsConstructor
public class PriceSchemeBusinessImpl implements PriceSchemeBusiness {
    
    final ModelUtil modelUtil;
    final HallServiceImpl hallService;
    final FilmServiceImpl filmService;
    final PriceSchemeServiceImpl service;
    final PriceSchemeHallMapServiceImpl hallMapService;
    final PriceSchemeFilmMapServiceImpl filmMapService;
    final PriceSchemeWeekServiceImpl priceSchemeWeekService;
    final PriceSchemeMemberPriceServiceImpl memberPriceService;
    
    @Override
    public PageModel<PriceSchemeListOutput> list(PriceSchemeListInput input) {
        IPage<PriceSchemeEntity> page = service.iPage(input);
        List<PriceSchemeListOutput> outputs = processPriceSchemes(page.getRecords());
        return new PageModel<>(page.getTotal(), outputs);
    }
    
    @Override
    public void add(PriceSchemeAddInput input) {
        
        Long cinemaId = CinemaIdUtil.getCinemaIdFromRequest();
        
        // 检查标题是否已存在
        if (service.count(new QueryWrapper<PriceSchemeEntity>().eq("title", input.getTitle()).eq("cinema_id", cinemaId)) > 0) {
            throw new BadRequestException(
                ErrorCode.RECORD_EXIST,
                EscapeUtil.errorMessage("title", "该标题已存在")
            );
        }
        
        // 新增基本数据
        PriceSchemeEntity entity = modelUtil.mapToMap(input, PriceSchemeEntity.class);
        entity.setAvailablePeriod(PriceSchemeAvailablePeriod.Companion.valueOf(input.getAvailablePeriod()));
        entity.setActivateType(PriceSchemeActivateType.Companion.valueOf(input.getActivateType()));
        entity.setCinemaId(cinemaId);
        entity.setIssuePrices(GsonUtil.getInstance().toJson(input.getIssuePrices()));
        service.save(entity);
        
        // 保存时间
        if (1 == input.getAvailablePeriod()){
            List<PriceSchemeWeekEntity> priceSchemeWeekEntities = mapWeekTimes(input.getWeekTimes(), entity.getId(), cinemaId);
            priceSchemeWeekService.saveBatch(priceSchemeWeekEntities);
        }
        
        // 保存关联数据
        saveRelationData(input, entity.getId());
        
        // 保存会员价关联数据
        saveMemberPriceData(input, entity.getId());
    }
    
    @Override
    public void upd(PriceSchemeUpdInput input) {
        if (service.isEmpty(input.getId())) {
            throw new BadRequestException(
                ErrorCode.RECORD_NOT_EXIST,
                ErrorCode.RECORD_NOT_EXIST.getReasonPhrase()
            );
        }
       
        // 检查标题是否已存在
        if (service.count(new QueryWrapper<PriceSchemeEntity>().eq("title", input.getTitle()).ne("id", input.getId())) > 0) {
            throw new BadRequestException(
                ErrorCode.RECORD_EXIST,
                EscapeUtil.errorMessage("title", "该标题已存在")
            );
        }
        
        
        PriceSchemeEntity entity = modelUtil.mapToMap(input, PriceSchemeEntity.class);
        entity.setAvailablePeriod(PriceSchemeAvailablePeriod.Companion.valueOf(input.getAvailablePeriod()));
        entity.setActivateType(PriceSchemeActivateType.Companion.valueOf(input.getActivateType()));
        entity.setIssuePrices(GsonUtil.getInstance().toJson(input.getIssuePrices()));
        service.updateById(entity);

        // 保存时间
        if (1 == input.getAvailablePeriod()){
            // 更新时间
            List<PriceSchemeWeekEntity> priceSchemeWeekEntities = mapWeekTimes(input.getWeekTimes(), input.getId(), entity.getCinemaId());
            priceSchemeWeekService.remove(new QueryWrapper<PriceSchemeWeekEntity>().eq("price_scheme_id", input.getId()));
            priceSchemeWeekService.saveBatch(priceSchemeWeekEntities);
        }
        
        // 更新影厅关联数据
        List<PriceSchemeHallMapEntity> hallMaps = input.getHallIds().stream()
            .map(hallId -> new PriceSchemeHallMapEntity(hallId, input.getId()))
            .collect(Collectors.toList());
        hallMapService.remove(new QueryWrapper<PriceSchemeHallMapEntity>().eq("price_scheme_id", input.getId()));
        hallMapService.saveBatch(hallMaps.size() > 0 ? hallMaps : List.of(new PriceSchemeHallMapEntity(0L, input.getId())));
        
        // 更新影片关联数据
        List<PriceSchemeFilmMapEntity> filmMaps = input.getFilmIds().stream()
            .map(filmId -> new PriceSchemeFilmMapEntity(filmId, input.getId()))
            .collect(Collectors.toList());
        filmMapService.remove(new QueryWrapper<PriceSchemeFilmMapEntity>().eq("price_scheme_id", input.getId()));
        filmMapService.saveBatch(filmMaps.size() > 0 ? filmMaps : List.of(new PriceSchemeFilmMapEntity(0L, input.getId())));
        
        // 更新会员价关联数据
        List<PriceSchemeMemberPriceEntity> memberPriceMaps = input.getMemberPriceInput().stream()
            .map(memberPriceDto -> new PriceSchemeMemberPriceEntity(memberPriceDto.getMemberLevelId(), PriceSchemeMemberPriceType.Companion.valueOf(memberPriceDto.getType()), memberPriceDto.getFirstEnable(), memberPriceDto.getValue(), input.getId()))
            .collect(Collectors.toList());
        memberPriceService.remove(new QueryWrapper<PriceSchemeMemberPriceEntity>().eq("price_scheme_id", input.getId()));
        memberPriceService.saveBatch(memberPriceMaps);
    }
    
    
    private List<PriceSchemeWeekEntity> mapWeekTimes(List<WeekTimeInput> inputWeekTimes, Long priceSchemeId, Long cinemaId) {
        List<PriceSchemeWeekEntity> weekTimes = new ArrayList<>();
        for (WeekTimeInput inputTime : inputWeekTimes) {
            PriceSchemeWeekEntity weekEntity = new PriceSchemeWeekEntity();
            weekEntity.setWeek(inputTime.getWeek());
            weekEntity.setCinemaId(cinemaId);
            weekEntity.setPriceSchemeId(priceSchemeId);
            weekEntity.setTimes(formatWeekTimes(inputTime.getTimes()));
            weekTimes.add(weekEntity);
        }
        return weekTimes;
    }
    
    private String formatWeekTimes(List<WeekTimeInput.WeekTime> inputWeekTimes) {
        return WeekTimeInput.getString(inputWeekTimes, false);
    }
    
    
    private void saveRelationData(PriceSchemeAddInput input, Long priceSchemeId) {
        List<PriceSchemeHallMapEntity> hallMaps = input.getHallIds().stream()
            .map(hallId -> new PriceSchemeHallMapEntity(hallId, priceSchemeId))
            .collect(Collectors.toList());
        
        if (!hallMaps.isEmpty()) {
            hallMapService.saveBatch(hallMaps);
        } else {
            hallMapService.save(new PriceSchemeHallMapEntity(0L, priceSchemeId));
        }
        
        List<PriceSchemeFilmMapEntity> filmMaps = input.getFilmIds().stream()
            .map(filmId -> new PriceSchemeFilmMapEntity(filmId, priceSchemeId))
            .collect(Collectors.toList());
        
        if (!filmMaps.isEmpty()) {
            filmMapService.saveBatch(filmMaps);
        } else {
            filmMapService.save(new PriceSchemeFilmMapEntity(0L, priceSchemeId));
        }
    }
    
    private void saveMemberPriceData(PriceSchemeAddInput input, Long priceSchemeId) {
        List<PriceSchemeMemberPriceEntity> memberPriceMaps = input.getMemberPriceInput().stream()
            .map(memberPriceDto -> new PriceSchemeMemberPriceEntity(
                memberPriceDto.getMemberLevelId(),
                PriceSchemeMemberPriceType.Companion.valueOf(memberPriceDto.getType()),
                memberPriceDto.getFirstEnable(),
                memberPriceDto.getValue(),
                priceSchemeId))
            .collect(Collectors.toList());
        if (!memberPriceMaps.isEmpty()) {
            memberPriceService.saveBatch(memberPriceMaps);
        }
    }
    
    
    public Map<Long, PriceSchemeSimpOutput> matchLabModels(List<PriceSchemeInput> inputs) {
        QueryWrapper<PriceSchemeEntity> queryWrapper = new QueryWrapper<>();
         // filter(PriceSchemeListOutput::getEnable) // 只考虑启用的票价方案
        queryWrapper.eq("enable", Boolean.TRUE);
        queryWrapper.orderByAsc("sale_price");
        queryWrapper.orderByDesc("id");
        
        List<PriceSchemeEntity> list = service.list(queryWrapper);
        List<PriceSchemeListOutput> priceSchemes = processPriceSchemes(list);
        
//        priceSchemes.forEach(System.out::println);
//        inputs.forEach(System.out::println);
        
        // 存放匹配结果的Map，键为对象ID，值为LabModel列表
        Map<Long, PriceSchemeSimpOutput> resultMap = new HashMap<>();
        
        // 遍历每个输入对象
        for (PriceSchemeInput input : inputs) {
//            System.out.println(WeekTimeOutput.isDateMatchingTimeRange(input.getShowTime(), priceSchemes.get(0).getWeekTimes()));
            // 根据影厅ID、影片ID、放映时间、发行价等条件匹配符合条件的票价方案
            PriceSchemeSimpOutput labModel = priceSchemes.stream()
                .filter(scheme -> scheme.getActivateType() == 0 || isDateTimeWithinValidity(input.getShowTime(), scheme.getStartDate(), scheme.getEndDate())) // 匹配有效期
                .filter(scheme -> scheme.getHallLabs().isEmpty() || scheme.getHallLabs().stream().anyMatch(lab -> lab.getId().equals(input.getHallId()) || lab.getId() == 0L))
                .filter(scheme -> scheme.getFilmLabs().isEmpty() || scheme.getFilmLabs().stream().anyMatch(lab -> lab.getId().equals(input.getFilmId()) || lab.getId() == 0L))
                .filter(scheme -> scheme.getAvailablePeriod() == 0 || WeekTimeOutput.isDateMatchingTimeRange(input.getShowTime(), scheme.getWeekTimes()))
                .filter(scheme -> scheme.getIssuePrices().isEmpty() || isIssuePriceApplicable(scheme.getIssuePrices(), input.getIssuePrice()))
                // 取第一个 因为源数据 已经排过序了
                .map(scheme -> new PriceSchemeSimpOutput(scheme.getId(), scheme.getTitle(), scheme.getSalePrice()))
                .findFirst()
                .orElse(null);
            // 将结果存放到结果Map中，键为对象ID
            resultMap.put(input.getObjectId(), labModel);
        }
        
        return resultMap;
    }
    
    // 判断给定的日期时间是否在有效期内，并且考虑到其中一个日期为空的情况
    public static boolean isDateTimeWithinValidity(LocalDateTime dateTime, LocalDate startDate, LocalDate endDate) {
        // 如果开始日期或结束日期为空，则直接匹配上
        if (startDate == null || endDate == null) {
            return true;
        }
        
        // 判断给定日期是否在有效期内
        return !dateTime.toLocalDate().isBefore(startDate) && !dateTime.toLocalDate().isAfter(endDate);
    }
    
    // 判断给定的发行价是否在适用发行价列表中（精确到小数点后两位）
    public static boolean isIssuePriceApplicable(List<BigDecimal> issuePrices, BigDecimal issuePrice) {
        // 将发行价保留两位小数
        issuePrice = issuePrice.setScale(2, RoundingMode.HALF_UP);
        
        // 遍历适用发行价列表，逐个与发行价比较
        for (BigDecimal price : issuePrices) {
            // 将列表中的发行价保留两位小数
            price = price.setScale(2, RoundingMode.HALF_UP);
            // 如果匹配成功，返回 true
            if (price.compareTo(issuePrice) == 0) {
                return true;
            }
        }
        
        // 如果列表中没有与发行价匹配的项，返回 false
        return false;
    }
    
    public List<PriceSchemeListOutput> processPriceSchemes(List<PriceSchemeEntity> priceSchemes) {
        List<Long> priceSchemeIds = priceSchemes.stream().map(PriceSchemeEntity::getId).collect(Collectors.toList());
        Map<Long, List<FilmEntity>> filmListMap = filmService.queryFilmsByPriceSchemeIds(priceSchemeIds);
        Map<Long, List<HallEntity>> hallListMap = hallService.queryHallsByPriceSchemeIds(priceSchemeIds);
        Map<Long, List<PriceSchemeMemberPriceEntity>> priceSchemeListMap = memberPriceService.listMemberPricesByPriceSchemeIds(priceSchemeIds);
        Map<Long, List<PriceSchemeWeekEntity>> longListMap = priceSchemeWeekService.listWeekEntitiesByPriceSchemeIds(priceSchemeIds);
        
        
        return priceSchemes.stream().map(priceScheme -> {
            PriceSchemeListOutput output = new PriceSchemeListOutput(priceScheme);
            
            output.setWeekTimes(longListMap.getOrDefault(priceScheme.getId(), new ArrayList<>())
                .stream()
                .map(weekEntity -> {
                    WeekTimeOutput weekTimeOutput = new WeekTimeOutput();
                    weekTimeOutput.setWeek(weekEntity.getWeek());
                    weekTimeOutput.setTimes(convertTimes(weekEntity.getTimes()));
                    return weekTimeOutput;
                })
                .collect(Collectors.toList()));
            
            output.setFilmLabs(filmListMap.getOrDefault(priceScheme.getId(), new ArrayList<>()));
            output.setHallLabs(hallListMap.getOrDefault(priceScheme.getId(), new ArrayList<>()));
            output.setMemberPrices(priceSchemeListMap.getOrDefault(priceScheme.getId(), new ArrayList<>()));
            
            return output;
        }).collect(Collectors.toList());
    }
    
    
    private List<WeekTimeOutput.WeekTime> convertTimes(String timesJson) {
        return WeekTimeOutput.getWeekTimes(timesJson);
    }
    
}
